#include "basic.h"
#include "io.h"
#include "connections.h"
#include "dirent.h"
#include "file_management.h"
#include "List/simclist_impl.h"
#include "query_management.h"
#include <malloc.h>

pthread_mutex_t file_myMutex;
int file_mutexState = 0;

void file_initializeMutex() {
	if (file_mutexState == 0) {
		pthread_mutex_init(&file_myMutex, NULL);
		file_mutexState = 1;
	}
}

int request_file(void *arguments) {
	file_arg_t *argu = (file_arg_t *) arguments;

	int *sockfd = enstablish_tcp_connection(argu->ip, argu->port);

	if (*sockfd == -1) {
		printf("Richiesta file fallita, ip %s non raggiungibile\n", argu->ip);
		close(*sockfd);
		free(sockfd);
		return -1; // richiesta fallita
	}

	if (writen(*sockfd, argu->nome_file, strlen(argu->nome_file)) < 0) {
		printf("Errore nella scambio file\n");
		close(*sockfd);
		free(sockfd);
		return -1;
	}

	printf("RICHIESTO FILE:");
	printf("%s \n", argu->nome_file);
	fflush(stdout);
	int success;
	if (read(*sockfd, &success, sizeof(int)) <= 0) {
		printf("Errore nella scambio file\n");
		close(*sockfd);
		free(sockfd);
		return -1;
	}

	if (success == -1) {
		printf("FILE NON PRESENTE SUL PEER\n");
		fflush(stdout);
		close(*sockfd);
		free(sockfd);
		close(*sockfd);
		free(sockfd);
		return -1;
	}

	char path[90] = { 0 };
	int dir_file = open("USER_IMP", O_RDONLY, 0666);
	read(dir_file, path, 40);
	close(dir_file);
	strcat(path, "/P2P/Ricevuti/");
	strncat(path, argu->nome_file, strlen(argu->nome_file));
	printf("%s\n", path);
	int file = open(path, O_CREAT | O_WRONLY | O_TRUNC, 0666);

	int n;
	long lunghezza = -1;
	char *mem = malloc(1000);
	memset(mem, 0, 1000);

	if (read(*sockfd, &lunghezza, sizeof(long)) <= 0) {
		printf("Errore nella scambio file\n");
		close(file);
		close(*sockfd);
		free(sockfd);
		return -1;
	}
	printf("%ld\n", lunghezza);
	while (lunghezza > 0 && ((n = read(*sockfd, mem, 1000)) > 0)) {
		if ((writen(file, mem, n)) < 0) { // scrivo i byte letti
			printf("Errore nella scambio file\n");
			close(file);
			close(*sockfd);
			free(sockfd);
			return -1;
		}
		memset(mem, 0, 1000);
		lunghezza -= n;
	}
	if (lunghezza == 0) {
		printf("RICEVUTO: ");
		printf("%s\n", argu->nome_file);
		fflush(stdout);
	} else {
		printf("Errore nella scambio file\n");
		printf("Ricezione non completata\n");
		close(file);
		close(*sockfd);
		free(sockfd);
		return -1;
	}
	close(file);
	close(*sockfd);
	free(sockfd);
	return 1; // richiesta andata a buon fine
}

void send_file(void *arguments) {
	file_arg_t *argu = arguments;

	char nome[80];
	if (read(argu->socket, nome, 60) <= 0) {
		perror("errore lettura nome file");
		if (close(argu->socket) == -1)
			perror("errore in close");
		return;
	}

	char *file_name2 = nome;

	argu->nome_file = file_name2;
	printf("%s", "RICERCA FILE:");
	printf("%s\n", argu->nome_file);

	char path[90] = { 0 };
	int dir_file = open("USER_IMP", O_RDONLY, 0666);
	read(dir_file, path, 40);
	close(dir_file);
	strcat(path, "/P2P/Condivisi/");
	strcat(path, argu->nome_file);

	printf("%s\n", path);

	int file = open(path, O_RDONLY);

	if ((writen(argu->socket, &file, sizeof(int))) < 0) { // se non riesco ad aprire il file lo comunico
		perror("errore in write su socket");
		if (close(argu->socket) == -1)
			perror("errore in close");
		return;
	}
	if (file == -1) {
		printf("%s\n", "FILE NON PRESENTE");
		if (close(argu->socket) == -1)
			perror("errore in close");
		return;
	}
	printf("APERTO FILE\n");
	fflush(stdout);
	int n = 0;
	long lunghezza = lseek(file, 0, SEEK_END);
	printf("%ld\n", lunghezza);

	fflush(stdout);
	lseek(file, 0, SEEK_SET);
	if ((writen(argu->socket, &lunghezza, sizeof(long))) < 0) {
		printf("errore in write su socket chiuso thread\n");
		if (close(argu->socket) == -1)
			printf("errore in close chiudo thread\n");
		return;
	}
	void *mem = malloc(1000);
	while ((n = readn(file, mem, 1000)) > 0) {
		if ((writen(argu->socket, mem, n)) < 0) { // invio a blocchi di 1000 byte
			printf("errore in write su socket chiudo thread\n");
			if (close(argu->socket) == -1)
				printf("errore in close\n");
			return;
		}
		memset(mem, 0, 1000);
	}

	printf("INVIATO:");
	printf("%s", argu->nome_file);
	printf("\n\n");
	fflush(stdout);
	close(file);
	free(mem);
	if (close(argu->socket) == -1) {
		printf("errore in close\n");
		return;
	}
	return;
}

void update_file(void *arguments) { // se peer deve spedire la lista altrimenti no
	update_arg_t *arg = arguments;
	char *superpeer_ip;
	struct in_addr sp;
	sp.s_addr = arg->superpeer_ip;
	superpeer_ip = inet_ntoa(sp);

	list_t *lista_file = arg->list;
	int *socket_update;
	int delete_this_list = -1;

	list_t temp_lista_file;
	list_init(&temp_lista_file);
	list_attributes_comparator(&temp_lista_file, list_comparator_string);
	list_attributes_seeker(&temp_lista_file, string_seeker);
	list_attributes_copy(&temp_lista_file, list_meter_string, 1);

	list_t update_list;
	list_init(&update_list);
	list_attributes_comparator(&update_list, list_comparator_string);
	list_attributes_seeker(&update_list, string_seeker);
	list_attributes_copy(&update_list, list_meter_string, 1);
	list_attributes_serializer(&update_list, element_serializer_string);


	size_t len;

	char path[90] = { 0 };
	int dir = open("USER_IMP", O_RDONLY, 0666);
	read(dir, path, 40);
	close(dir);
	strcat(path, "/P2P/Condivisi");

	char command[150] = { 0 }; // conto num file in cartella
	strcpy(command, "ls -l ");
	strcat(command, path);
	strcat(command, " | wc -l");
	FILE *f;

	DIR *directory = opendir(path);
	struct dirent *name_list;
	struct timespec time;

	int file_number = 0;
	char output[10];

	int size_list;
	int i;

	struct stat buff;

	stat(path, &buff);
	struct timespec last_time = buff.st_mtim; // tempo di ultimo accesso

	while ((name_list = readdir(directory)) != NULL) {

		if (strcmp(name_list->d_name, ".") != 0 && strcmp(name_list->d_name,
				"..") != 0) {

			char *elem_seek = (char*) list_seek(lista_file, name_list->d_name);
			if (elem_seek == NULL) {
				list_append(lista_file, name_list->d_name);
				list_sort(lista_file, 1);
				printf("aggiungere %s\n", name_list->d_name);
				list_append(&update_list, name_list->d_name);
			}
		}
	}

	if (list_size(lista_file) != 0 && arg->is_superpeer == 0) { // se aggiorno la lista e non e' superpeer deve spedire
		printf("Invio aggiornamenti insert al superpeer \n");
		delete_this_list = 0; // comunica di aggiungere i file nella lista
		list_dump_file(&update_list, "UPDATE_LIST", &len);
		socket_update = enstablish_tcp_connection(superpeer_ip,
				UPDATE_FILE_PORT);
		write(*socket_update, &delete_this_list, sizeof(int));
		send_list("UPDATE_LIST", *socket_update);
		close(*socket_update);
		free(socket_update);
	}
	list_clear(&update_list);
	closedir(directory);

	while (arg->is_superpeer || *(arg->is_alive) == 1) { // aggiornamento della lista se necessario

		stat(path, &buff);
		time = buff.st_mtim;

		if (time.tv_sec > last_time.tv_sec) { // aggiorno files

			sleep(1);

			f = popen(command, "r"); // calcolo numero file in cartella Condivisi
			fread(output, sizeof(int), 20, f);
			fclose(f);
			file_number = atoi(output) - 1;

			size_list = list_size(lista_file);

			if (size_list > file_number) {

				directory = opendir(path);

				while ((name_list = readdir(directory)) != NULL) {

					if (strcmp(name_list->d_name, ".") != 0 && strcmp(
							name_list->d_name, "..") != 0) {
						list_append(&temp_lista_file, name_list->d_name);
					}
				}

				while (size_list > file_number) {

					for (i = 0; i < size_list; i++) {
						char *e = (char *) list_get_at(lista_file, i);
						char *seek_elem = list_seek(&temp_lista_file, e);
						if (seek_elem == NULL) {
							printf("Cancellato alla mia lista %s\n", e);
							list_append(&update_list, e);
							list_delete(lista_file, e);
							list_sort(lista_file, 1);
							break;
						}
					}
					size_list = list_size(lista_file);
				}

				if (arg->is_superpeer == 0) { // se non e' superpeer deve spedire la lista
					printf("Invio aggiornamenti delete al superpeer \n");
					delete_this_list = 1; // comunica di cancellare i file nella lista
					list_dump_file(&update_list, "UPDATE_LIST", &len);
					socket_update = enstablish_tcp_connection(superpeer_ip,
							UPDATE_FILE_PORT);
					write(*socket_update, &delete_this_list, sizeof(int));
					send_list("UPDATE_LIST", *socket_update);
					close(*socket_update);
					free(socket_update);
				}
				list_clear(&update_list);

				list_clear(&temp_lista_file);
				closedir(directory);

			}

			else if (size_list < file_number) {
				directory = opendir(path);

				while ((name_list = readdir(directory)) != NULL) {

					if (strcmp(name_list->d_name, ".") != 0 && strcmp(
							name_list->d_name, "..") != 0) {

						char *elem_seek = (char*) list_seek(lista_file,
								name_list->d_name);
						if (elem_seek == NULL) {
							list_append(lista_file, name_list->d_name);
							list_sort(lista_file, 1);
							printf("Aggiunto alla mia lista %s\n",
									name_list->d_name);
							list_append(&update_list, name_list->d_name);
						}
					}
				}

				if (arg->is_superpeer == 0) { // se non e' superpeer deve spedire la lista
					printf("Invio aggiornamenti insert al superpeer \n");
					delete_this_list = 0; // comunica di aggiungere i file nella lista
					list_dump_file(&update_list, "UPDATE_LIST", &len);
					socket_update = enstablish_tcp_connection(superpeer_ip,
							UPDATE_FILE_PORT);
					write(*socket_update, &delete_this_list, sizeof(int));
					send_list("UPDATE_LIST", *socket_update);
					close(*socket_update);
					free(socket_update);
				}
				list_clear(&update_list);

				closedir(directory);
			}

			last_time.tv_sec = time.tv_sec;

		}
	}
}

char* filename_filter(char* file_name) {

	int p = 0;
	while (1) {
		if (file_name[p] != '\n')
			p++;
		else
			break;
	}
	char *file_name2 = malloc(p + 1);
	int k;
	for (k = 0; k < p; k++) {
		file_name2[k] = file_name[k];
	}
	file_name2[p] = '\0';

	return file_name2;

}

void update_list(void *arguments) {

	file_initializeMutex();
	update_arg_t *arg = arguments;
	int to_delete = -1;

	list_t *update_list = malloc(sizeof(list_t));
	list_init(update_list);

	int n;
	size_t lunghezza_lista;

	read(arg->socket, &to_delete, sizeof(int));

	if (read(arg->socket, &lunghezza_lista, sizeof(size_t)) < 0) {
		perror("ERRORE");
	}
	printf("%zu  peso lista da aggiornare\n", lunghezza_lista);
	void *mem = malloc(1000);
	int file_list = open("UPDATE_LIST", O_RDWR | O_CREAT, 0666);
	while (lunghezza_lista > 0 && ((n = read(arg->socket, mem, 1000)) > 0)) {
		if ((write(file_list, mem, n)) < 0) { // invio a blocchi di 1000 byte
			perror("errore in write su socket");
			return;
		}
		memset(mem, 0, 1000);
		lunghezza_lista -= n;
	}
	free(mem);

	close(file_list);
	if (list_restore_file(update_list, "UPDATE_LIST", &lunghezza_lista) < 0) {
		perror("ERRORE IN list_restore_file");
		return;
	}

	printf("%d  lunghezza lista\n", list_size(update_list));

	if (to_delete == 1) {
		struct in_addr a; // print check
		a.s_addr = arg->ip; // print check
		printf("RICHESTA CANCELLAZIONE FILE PER: %s\n", inet_ntoa(a)); // print check
		list_iterator_start(update_list);
		while (list_iterator_hasnext(update_list)) {
			char *file = ((char *) (list_iterator_next(update_list)));
			pthread_mutex_lock(&file_myMutex);
			delete_file(arg->list, file, arg->ip);
			pthread_mutex_unlock(&file_myMutex);
			printf("Cancellato: %s per %s", file, inet_ntoa(a));
		}
		list_iterator_stop(update_list);
		printf("FINE CANCELLAZIONE\n");

	}

	else if (to_delete == 0) {
		struct in_addr a;
		a.s_addr = arg->ip;
		printf("RICHESTA AGGIUNTA FILE PER: %s\n", inet_ntoa(a)); // print check
		list_iterator_start(update_list);
		while (list_iterator_hasnext(update_list)) {
			char *file = ((char *) (list_iterator_next(update_list)));
			pthread_mutex_lock(&file_myMutex);
			add_file(arg->list, file, arg->ip);
			pthread_mutex_unlock(&file_myMutex);
			printf("Aggiunto file: %s per %s\n", file, inet_ntoa(a));
		}
		list_iterator_stop(update_list);
		printf("FINE AGGIORNAMENTO\n");
	}
	printf("LISTA CORRENTE:\n");
	print_file_list(arg->list);
	free(update_list);
}

